/*
 * SRMI (an easy-to-use zero-code remoting library for Java)
 * Copyright (C) 2007  Mohammad Nabil Hussein
 * mohammad (dot) nabil (dot) h (at) gmail (dot) com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package SRMI.RemotingServices.ClassGeneration.Server;

import SRMI.RemotingServices.ClassGeneration.ClassGenerator;
import SRMI.RemotingServices.ClassGeneration.PassByRemoteStubGenerator;
import SRMI.RemotingServices.ClassGeneration.RemoteInterfaceGenerator;
import SRMI.RemotingServices.PassableByRemote;
import SRMI.SRMIException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

/**
 * The class that generates the rmi service wrapper.
 * @author meemo
 */
public class RmiServiceWrapperGenerator extends ClassGenerator
{
    private Class remoteInterface;
    protected Class remoteExceptionClass;
    
    /**
     * @param wrapped The class to generate rmi service wrapper for.
     */
    public RmiServiceWrapperGenerator(Class wrapped) throws SRMIException
    {
        Class remoteInterface = new RemoteInterfaceGenerator(wrapped).GetGenerated();
        
        this.setWrappedClass(wrapped);
        this.remoteInterface = remoteInterface;
        assert remoteInterface != null : "null interface class";
        try
        {
            remoteExceptionClass = Class.forName("java.rmi.RemoteException");
        }
        catch (ClassNotFoundException ex)
        {
            ex.printStackTrace();
        }
    }
    
    protected void PrepareEmit()
    {
        PrepareDefaultWrapperEmit();
    }
    
    protected void EmitSupers()
    {
        file.print("extends java.rmi.server.UnicastRemoteObject implements " + remoteInterface.getCanonicalName());
    }
    
    protected void EmitConstructor()
    {
        file.print(String.format(
                "\tpublic %s(%s wrapped) throws java.rmi.RemoteException\n\t{\n\t\tthis.wrapped = wrapped;\n\t}\n\n",generatedClassName,wrappedFullyQualifiedClassName));
    }
    
    @Override
    protected void EmitFunctions() throws SRMIException
    {
        for(Method m : remoteInterface.getMethods())
            EmitFunction(m);
    }
    
    protected void EmitMembersVariables()
    {
        file.print("\t" + getWrappedClass().getCanonicalName() + " wrapped;\n\n");
    }
    
    protected String getGeneratedClassNameSuffix()
    {
        return "_serverside_wrapper";
    }
    
    protected void EmitFunction(Method m) throws SRMIException
    {
        /*
         public int Quack(int i) throws java.rmi.RemoteException
            {
                return this.wrapped.Quack(i);
            }
         */
        int mod = m.getModifiers();
        if(!Modifier.isPrivate(mod)
        && !Modifier.isStatic(mod)
        && !Modifier.isFinal(mod)
        )
        {
            EmitModifiers(mod);
            file.print(" ");
            EmitType(m.getReturnType());
            file.print(" ");
            file.print(m.getName());
            file.print("(");
            EmitArgs(m, false);
            file.print(")");
            ArrayList<Class> exceps = new ArrayList<Class>();
            for(Class c : m.getExceptionTypes())
                exceps.add(c);
            // exceps.add(remoteExceptionClass); // i don't need to explicitly
            // the remoteException because i implement the remote interface which
            // already does that ( already implicitly there )
            EmitExceptions(exceps.toArray(new Class[]{}) );
            file.print("\n\t{\n");
            
            EmitWrappedCall(m);
            
            file.print("\n");
            
            file.print("\t}\n\n");
        }
    }
    
    protected void EmitWrappedCall(Method m) throws SRMIException
    {
        file.print(
                String.format(
                "\t\t%swrapped.%s(",
                ( m.getReturnType().equals(java.lang.Void.TYPE)?"":(m.getReturnType().getName() + " ret_Obj = ")),
                m.getName( ) ) ) ;
        EmitArgs(m,true);
        file.print(");\n\n");
        
        if(m.getReturnType().getAnnotation(PassableByRemote.class) != null)
        {
            // return-by-remote case
            Class retType = m.getReturnType();
            file.print(String.format(
                        "\t\t%s ret_ByRemoteObj = null;\n" +
                        "\t\tif(ret_Obj instanceof %s_pass_by_remote_stub)\n" +
                        "\t\t\ttry {\n" +
                        "\t\t\t\tSystem.out.println(\"resolving (return) by-remote-to-remote reference to remote-reference\");\n" +
                        "\t\t\t\tret_ByRemoteObj = ((%s)((%s_pass_by_remote_stub)ret_Obj).clone());\n" +
                        "\t\t\t} catch (Exception ex) {}\n" +
                        "\t\telse\n" +
                        "\t\t\tret_ByRemoteObj = ret_Obj != null ? new %s_pass_by_remote_stub(ret_Obj) : null;\n\n",
                    retType.getName(),retType.getName(),retType.getName(),retType.getName(),retType.getName() ));
            file.println("\t\treturn ret_ByRemoteObj;\n");
        }
        else if(!m.getReturnType().equals(java.lang.Void.TYPE))
        {
            file.println("\t\treturn ret_Obj;\n");
        }
        
        
        // just assure thre pass-by-remote classes are generated
        //////////////////////////////////////////////
        for(Class<?> param : m.getParameterTypes())
            if(param.getAnnotation(PassableByRemote.class) != null)
                // assure it is generated
                new PassByRemoteStubGenerator(param).Prepare();
        if(m.getReturnType().getAnnotation(PassableByRemote.class) != null)
            new PassByRemoteStubGenerator(m.getReturnType()).Prepare();
    }
}
